home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Mac-Source 1994 July
/
Mac-Source_July_1994.iso
/
C and C++
/
Graphics⁄Sound
/
Macintosh Tracker Folder
/
Tracker Server Folder
/
mac_audio.c
< prev
next >
Wrap
Text File
|
1993-09-19
|
39KB
|
1,276 lines
/* macintosh_audio.c */
/* All the stuff in this file was written by Thomas R. Lawrence. */
/* See the "mac_readme" or "mac_programmer_info" files for more information */
/* about the Macintosh port */
#include <Sound.h>
#include <sane.h>
#include "mac_event.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "defs.h"
#include "extern.h"
#include "song.h"
#include "channel.h"
#define CONSTRAIN(value,min,max) MAX(MIN(value,max),min)
#define MAXNUMBUFFERS (128)
#define MAXBUFFERSIZE (8192)
#define MinFreeMem (32768 + MAXBUFFERSIZE + 256)
/* comment this out to use C-code */
#define USE_ASSEMBLY_CODE (1) /* 1 = yes, 0 = no */
typedef struct MyS
{
struct MyS* Next; /* circular linked list embedded in a static array */
SndCommand MySoundCommand;
SndCommand MyCallbackCommand;
volatile short InUseFlag; /* interrupt level flag; hence "volatile" */
ExtSoundHeader Header;
char SampleArea[MAXBUFFERSIZE];
} MyStructure;
static pascal void MyCallBack(SndChannel* Channel, SndCommand* Command);
extern Boolean QuitPending;
static int mac_stereo;
/* 256th of primary/secondary source for that side. */
static int primary=256, secondary=0;
extern short Loudness;
/* the following array is grouped as 16*256 2-byte words. Bits 9..12 of the */
/* index are the most significant bits of the fraction of the pointer for */
/* anti-aliasing. Bits 1..8 are the sample's value. If Bit 0 of the address is */
/* 0, then this is the partially weighted left hand side for the antialiasing */
/* average. Bit 0 == 1 is the right hand side. The left hand side is */
/* the fraction * sample. The right hand side is 2s complement of fraction * sample */
static char* AliasTable = NULL;
/* the following array is a 1024-byte array for converting samples overall loudness */
/* with clipping instead of roll-over. This is because each raw sample value */
/* ranges from -128..127. We convert this to unsigned for the table: 0..255 */
/* There are 4 channels, so the maximum is 4*255 == 1020. Thus, we provide */
/* 1024 (a nice round number) values. When the table is constructed, the */
/* values are constrained (i.e. clipped) so that rollover doesn't occur. This */
/* makes the sound a bit better when the volume is high enough to cause */
/* rollover. The maximum value of abs(FinalVolumeTablePrimary) is "primary" */
/* and the max of abs(FinalVolumeTableSecondary) is "secondary". See */
/* "resample" for usage notes */
static char* FinalVolumeTablePrimary = NULL;
static char* FinalVolumeTableSecondary = NULL;
/* the following array is a 0..64*256 entry array for converting volume */
/* (in the upper bits) and an 8-bit sample value into a new sample value. */
/* an upper value of 64 represents no scaling. */
/* You use this like this: */
/* SubVolumeTable[<8-bit sample> + 256 * <6-bit volume level>] */
static short* SubVolumeTable = NULL;
/* the following array is used for workspace */
static long WorkspaceBytes = 0;
static short* WorkspaceArray1 = NULL;
static short* WorkspaceArray2 = NULL;
static SndChannel* MySoundChannel; /* here's where it all happens, folks */
static MyStructure* Buffers[MAXNUMBUFFERS];
static int ActualNumBuffers;
static MyStructure* CurrentBuffer;
static int buf_index;
short Pausing = false;
/* this is dragged in from "mac_event" */
extern short NumBits; /* what they want */
static int ActualNumBits; /* what we're actually using */
/* this is called from the event cycle which is called from the synthesizer via */
/* the invocation of "may_getchar()". When the program is playing */
/* (Pausing == false), it sets Pausing to true, pauses the Macintosh sound */
/* channel, and then spinwaits, calling may_getchar to provide processing time */
/* to other programs. When the event to unpause is received, this program is */
/* invoked again (the first activation record is still present--recursive */
/* invocation). The second call unpauses the sound channel and clears the flag. */
/* As the call stack is unrolled, control will be returned here. Since the */
/* Pausing flag is now clear, this routine will exit and things will carry */
/* on normaly. FLAW: pauseCmd does not seem to have an effect. The buffered */
/* sound will play out and sound will stop only when all prepared buffers */
/* have been played. I don't know how to fix this; it seems to be a problem in */
/* the Macintosh OS, not in this program. */
void TogglePause(void)
{
SndCommand MyCommand;
if (Pausing)
{
Pausing = false;
MyCommand.cmd = resumeCmd;
MyCommand.param1 = 0;
MyCommand.param2 = 0;
SndDoImmediate(MySoundChannel,&MyCommand);
}
else
{
Pausing = true;
MyCommand.cmd = pauseCmd;
MyCommand.param1 = 0;
MyCommand.param2 = 0;
SndDoImmediate(MySoundChannel,&MyCommand);
while (Pausing && !QuitPending)
{
may_getchar();
}
}
}
/* adjust the stereo mixing. Percent is 0..100; each channel takes on a value */
/* in 0..256, providing 8-bits of scaling. At full volume for a channel, */
/* 256 represents a shift left by 8. Thus, a shift right by 8 is the necessary */
/* correction after scaling. "primary" is for the main channel and "secondary" */
/* is for the other channel. They do not correspond strictly to left and right. */
void set_mix(int percent)
{
percent *= (256/2);
percent /= 100;
secondary = percent;
primary = 256 - percent;
}
/* recalibrate the volume lookup table using a new value. This is done on */
/* the fly as volume levels are adjusted. */
void ResetVolumeTable(void)
{
short Scan;
short Intermediate;
short Radius;
if (FinalVolumeTablePrimary == NULL)
{
FinalVolumeTablePrimary = (char*)NewPtr(1024 * sizeof(char));
if (FinalVolumeTablePrimary == NULL)
{
perror("Not enough memory to play song.");
end_all();
}
}
/* primary ranges between 0..255; abs(sample) ranges between 0..127. */
/* so we divide by 2 and subtract 1 just in case -128 gets in. The radius */
/* is the maximum value we'll allow the sample to be. Anything above */
/* is clipped. Primary Radius + Secondary Radius == 127 (barring rounding */
/* errors.) Thus we clip just before arithmetic rollover occurs. */
Radius = (primary / 2) - 1;
if (Radius < 0)
{
Radius = 0;
}
for (Scan = -512; Scan <= 511; Scan += 1)
{
Intermediate = (Scan * Loudness * primary) / (64 * 256);
if (Intermediate > Radius)
{
Intermediate = Radius;
}
if (Intermediate < -Radius)
{
Intermediate = -Radius;
}
FinalVolumeTablePrimary[Scan & 0x03ff] = Intermediate;
}
if (FinalVolumeTableSecondary == NULL)
{
FinalVolumeTableSecondary = (char*)NewPtr(1024 * sizeof(char));
if (FinalVolumeTableSecondary == NULL)
{
perror("Not enough memory to play song.");
end_all();
}
}
Radius = (secondary / 2) - 1;
if (Radius < 0)
{
Radius = 0;
}
for (Scan = -512; Scan <= 511; Scan += 1)
{
Intermediate = (Scan * Loudness * secondary) / (64 * 256);
if (Intermediate > Radius)
{
Intermediate = Radius;
}
if (Intermediate < -Radius)
{
Intermediate = -Radius;
}
FinalVolumeTableSecondary[Scan & 0x03ff] = Intermediate;
}
}
int open_audio(int SampleRate, int StereoFlag)
{
OSErr Error;
short Scan;
short Index;
SndCommand Cmd;
long double Fred;
unsigned long FixedSampleRate;
MyStructure* LastBuffer;
if ((NumBits != 8) && (NumBits != 16))
{
NumBits = 8;
}
ActualNumBits = NumBits;
FixedSampleRate = ((long)SampleRate) << 16;
if (FixedSampleRate == 0)
{
FixedSampleRate = rate22khz;
}
if (AliasTable == NULL)
{
AliasTable = (char*)NewPtr(16*256*2);
if (AliasTable == NULL)
{
perror("Not enough memory to play song.");
end_all();
}
for (Scan = 0; Scan <= 15; Scan += 1)
{
for (Index = -128; Index <= 127; Index += 1)
{
short TableIndex;
/* we precompute the antialiasing multiplication table here. */
/* Scan == the high 4 bits of the fraction of the pointer which */
/* is accessing the sampled sound. Thus 0 IS a sample point, */
/* 1 is just to the right of a sample point, and 15 is just to */
/* the left of a sample point. As we approach the right, the */
/* value increases, so the right weight is just Scan. As we */
/* approach the left, the weight increases, but Scan decreases */
/* so the value is 16-Scan. When Scan == 0, the weight of the */
/* right is 0, and the full left value is used. This is because */
/* at this point, the left sample is being pointed directly to */
/* so it's actual value should be returned. */
TableIndex = ((Scan << 8) | (Index & 0x00ff)) << 1;
AliasTable[TableIndex + 0] = ((16 - Scan) * Index) / 16;
AliasTable[TableIndex + 1] = (Scan * Index) / 16;
}
}
}
ResetVolumeTable();
if (SubVolumeTable == NULL)
{
SubVolumeTable = (short*)NewPtr((MAX_VOLUME-MIN_VOLUME+1)*256*sizeof(short));
if (SubVolumeTable == NULL)
{
perror("Not enough memory to play song.");
end_all();
}
for (Scan = 0; Scan <= MAX_VOLUME - MIN_VOLUME; Scan += 1)
{
for (Index = -128; Index <= 127; Index += 1)
{
SubVolumeTable[(Scan << 8) | (Index & 0x00ff)]
= ((Scan + MIN_VOLUME) * Index + ((MAX_VOLUME - MIN_VOLUME)/2))
/ (MAX_VOLUME - MIN_VOLUME);
/* normally, that would be MAX_VOLUME - MIN_VOLUME + 1, but */
/* we want to be 0..1 instead of 0..0.99999 because MAX_VOLUME */
/* should not scale the volume at all. */
}
}
}
mac_stereo = StereoFlag;
Error = SndNewChannel(&MySoundChannel,
sampledSynth, /* what synthesizer to use */
(StereoFlag * initStereo)
| initNoInterp, /* we do oversampling ourselves */
(void*)MyCallBack /* == our buffer unmarking callback routine */);
if (Error != noErr)
{
perror("unable to open sound channel");
end_all();
}
Cmd.cmd = ampCmd;
Cmd.param1 = 255;
Cmd.param2 = 0;
Error = SndDoImmediate(MySoundChannel,&Cmd);
if (Error != noErr)
{
perror("unable to initialize sound channel");
}
/* create an initial workspace */
WorkspaceBytes = (1500 * sizeof(short) + 3) & ~3L; /* should be plenty */
WorkspaceArray1 = (void*)NewPtr(WorkspaceBytes);
WorkspaceArray2 = (void*)NewPtr(WorkspaceBytes);
if ((WorkspaceArray1 == NULL) || (WorkspaceArray2 == NULL))
{
FatalError(FatalErrorOutOfMemory);
perror("No memory to allocate workspace.");
end_all();
}
/* we now try to make as many buffers as we have memory for, so we can */
/* precompute the composite samples to be played as far ahead as we can */
/* so that momentary losses of control don't make the sound skip. We */
/* need at least 3 buffers, for double buffering and to make our full */
/* usage check work. (We check to see if all buffers are waiting to be played */
/* and not empty by counting the number of ones with a marked "InUseFlag" */
/* However, one buffer (the one under construction) will always be NOT */
/* marked. If we settle for 2 buffers, then when we account for the buffer */
/* we are constructing, it will always look like all buffers are in use */
/* until all have stopped. The sound will skip, so we might as well not */
/* play it at all. Hence the limit of 3 buffers.) */
Scan = 0;
do
{
MyStructure* Temp;
long FreeMemory;
FreeMemory = FreeMem();
if (FreeMemory < MinFreeMem)
{
if (Scan < 3)
{
perror("Not enough memory to play song!");
FatalError(FatalErrorOutOfMemory);
FatalError(FatalErrorOutOfMemory);
end_all();
}
else
{
goto StopMakingBuffers;
}
}
Buffers[Scan] = (void*)NewPtr(sizeof(MyStructure));
Buffers[Scan]->InUseFlag = 0;
Buffers[Scan]->Header.samplePtr = &(Buffers[Scan]->SampleArea[0]);
if (mac_stereo)
{
Buffers[Scan]->Header.sampleSize = ActualNumBits;
Buffers[Scan]->Header.numChannels = 2;
}
else
{
Buffers[Scan]->Header.sampleSize = ActualNumBits;
Buffers[Scan]->Header.numChannels = 1;
}
Buffers[Scan]->Header.sampleRate = FixedSampleRate;
Buffers[Scan]->Header.loopStart = 0;
Buffers[Scan]->Header.loopEnd = 0;
Buffers[Scan]->Header.encode = extSH;
Buffers[Scan]->Header.baseFrequency = 64;
Buffers[Scan]->Header.markerChunk = NULL;
Buffers[Scan]->Header.futureUse1 = 0;
Buffers[Scan]->Header.futureUse2 = 0;
Buffers[Scan]->Header.futureUse3 = 0;
Buffers[Scan]->Header.futureUse4 = 0;
Fred = SampleRate;
x96tox80(&Fred,&(Buffers[Scan]->Header.AIFFSampleRate));
Scan += 1;
} while (Scan < MAXNUMBUFFERS);
StopMakingBuffers:
ActualNumBuffers = Scan;
for (Scan = 0; Scan < ActualNumBuffers; Scan += 1)
{
/* now we establish the circular linked list so that we can */
/* easily find out what the "next" buffer to use is */
Buffers[Scan]->Next = Buffers[(Scan + 1) % ActualNumBuffers];
}
CurrentBuffer = Buffers[0];
buf_index = 0;
return FixedSampleRate >> 16;
}
/* this evaluates to see if all but the current buffer have been filled */
/* and are waiting to be played. If they have, then we can take a break in */
/* the event loop since there won't be any work to do for a while. */
BOOL is_channel_full(void)
{
int Scan;
int Count;
Count = 0;
for (Scan = 0; Scan < ActualNumBuffers; Scan += 1)
{
if (Buffers[Scan]->InUseFlag)
{
Count += 1;
}
}
if (Count >= ActualNumBuffers - 1)
{
return TRUE;
}
else
{
return FALSE;
}
}
/* here we check to see if all the buffers have played out. This is done */
/* at the end so that we don't close the channel too early and cut off the */
/* end of the song. */
BOOL is_channel_empty(void)
{
int Scan;
for (Scan = 0; Scan < ActualNumBuffers; Scan += 1)
{
if (Buffers[Scan]->InUseFlag)
{
return FALSE; /* nope, buffers are still in use */
}
}
return TRUE;
}
/* this counts the number of buffers waiting to be played. The event loop uses */
/* this to get an idea of how long it can sit around before worrying about */
/* filling some more buffers. */
short NumberPendingBlocks(void)
{
int Scan;
int Count;
Count = 0;
for (Scan = 0; Scan < ActualNumBuffers; Scan += 1)
{
if (Buffers[Scan]->InUseFlag)
{
Count += 1;
}
}
return Count;
}
/* queues the data to be played by the sound manager. */
void actually_flush_buffer(void)
{
OSErr Error;
while (CurrentBuffer->Next->InUseFlag)
{
/* since the list is a circular list, the next element is the "oldest" */
/* element. If it is full, then all the others are too, so we just wait. */
/* if all the buffers have been filled, then we'll just spinwait */
/* here for 0.25 of a second waiting for one to play out. This could */
/* be a problem. If the number of buffers is small, then they might */
/* appear to be full, but they could still play out in less than 0.25 */
/* of a second. See also "may_getchar" where a similar thing is done. */
WaitForEvent(15);
}
TryAgainPoint1:
CurrentBuffer->InUseFlag = 1;
CurrentBuffer->MySoundCommand.cmd = bufferCmd;
CurrentBuffer->MySoundCommand.param1 = 0;
CurrentBuffer->MySoundCommand.param2 = (long)&(CurrentBuffer->Header);
if (mac_stereo)
{
/* stereo sample frames have 2 "samples" in them, so the index */
/* into the buffer is twice the number of frames. */
CurrentBuffer->Header.numFrames = buf_index / (2 * (ActualNumBits / 8));
}
else
{
CurrentBuffer->Header.numFrames = buf_index / (1 * (ActualNumBits / 8));
}
Error = SndDoCommand(MySoundChannel,&(CurrentBuffer->MySoundCommand),1);
if (queueFull == Error)
{
WaitForEvent(2);
goto TryAgainPoint1;
}
/* the callback is an interrupt level thing that clears a buffer so */
/* we can use it again. */
TryAgainPoint2:
CurrentBuffer->MyCallbackCommand.cmd = callBackCmd;
CurrentBuffer->MyCallbackCommand.param2 = (long)&(CurrentBuffer->InUseFlag);
Error = SndDoCommand(MySoundChannel,&(CurrentBuffer->MyCallbackCommand),1);
if (queueFull == Error)
{
WaitForEvent(2);
goto TryAgainPoint2;
}
buf_index = 0;
CurrentBuffer = CurrentBuffer->Next;
}
/* this function is not actually used */
void output_samples(int left, int right)
{
/* in the normal tracker, this is called EVERY TIME a sample frame is */
/* output. It normally results in a call to flush_buffer. Since */
/* THINK C won't inline functions, this was way to slow, so I dispensed with it */
}
/* this flushes the buffer IF another cycle would overflow it. Since the number */
/* of bytes added to the buffer is SamplingRate / Speed, at high sampling rates */
/* or ridiculously low speeds, larger than the buffer size could be created in */
/* just one call to "resample." This would be BAD. Therefore "resample" checks */
/* for this and returns a fatal error if it would happen. */
void flush_buffer(int BytesGenerated)
{
if (buf_index + BytesGenerated >= MAXBUFFERSIZE - 1)
actually_flush_buffer();
}
/* waiting for all samples to play and then closing sound channel & disposing buffers */
void close_audio(void)
{
int Scan;
EventRecord StupidEvent;
if (buf_index != 0)
{
/* make sure that last 1/8 of a second worth of song gets played. */
actually_flush_buffer();
}
/* wait for all buffers to play out */
/* if we receive a quit event, we just go away */
/* but for end of song, we wait for the buffers to play out */
/* SndDisposeChannel does this, but we want to be friendly to other */
/* processes by calling WaitNextEvent(), so we do it ourselves. */
while (!QuitPending && !is_channel_empty())
{
WaitForEvent(60);
}
/* close macintosh sound channel, cancel any callbacks */
#if 0
/* I used to be using this quietCmd which, according to the great Inside */
/* Macintosh VI, should stop all processing on the channel. Unfortunately, */
/* when I perform the call, the channel promptly becomes useless and the */
/* system hangs in the SndDisposeChannel routine waiting for some event */
/* that'll never happen (I have no idea what, but MacsBug clearly shows the */
/* infinite loop in system code). */
if (QuitPending)
{
SndCommand Cmd;
Cmd.cmd = quietCmd;
Cmd.param1 = 0;
Cmd.param2 = 0;
SndDoImmediate(MySoundChannel,&Cmd);
}
#endif
/* if we are supposed to quit right away, then we send 1 as the second */
/* parameter to do it right away, otherwise it'll wait for everything to */
/* play out before closing the channel */
SndDisposeChannel(MySoundChannel,QuitPending);
/* release memory allocated for buffers */
for (Scan = 0; Scan < ActualNumBuffers; Scan += 1)
{
/* this isn't really necessary unless you play more than one song */
/* at a time and the buffer is flushed each time. */
DisposPtr((void*)Buffers[Scan]);
}
}
/* what is this for? */
void set_synchro(int s)
{
}
/* another mystery function */
int update_frequency(void)
{
return 0;
}
void discard_buffer(void)
{
}
#if __option(profile)
#define Profiling
#endif
#pragma options(!profile)
/* asynchronous callback routine which marks buffers as now unused */
/* It would be very bad to profile this since profiling tampers with the */
/* stack invocation and uses A5 global variables! (believe me, I've tried) */
static pascal void MyCallBack(SndChannel* Channel, SndCommand* Command)
{
*(short*)(Command->param2) = 0;
}
#ifdef Profiling
#pragma options(profile)
#endif
/* stuff removed from "audio.c" */
/* prototypes for my single channel resamplers have been added */
void SampleAntiAliased8(struct channel* ch, short* Buffer, short Count);
void SampleAliased8(struct channel* ch, short* Buffer, short Count);
void SampleAntiAliased16(struct channel* ch, short* Buffer, short Count);
void SampleAliased16(struct channel* ch, short* Buffer, short Count);
/* a new "resample" function using my single channel resamplers has been added */
void resample(struct channel *chan, int oversample, int number)
{
short i;
short* WA1Shadow;
short* WA2Shadow;
short BytesGenerated;
/* flush the stuff to the buffer. i.e. if we would overflow the */
/* buffer during this cycle, we flush it and get a new buffer. */
/* later on, checks are performed to make sure we won't overflow the buffer. */
/* the only time that would happen would be if BytesGenerated is bigger */
/* than the whole buffer. */
/* note that flush_buffer now takes a parameter */
if (mac_stereo)
{
BytesGenerated = number * 2 * (ActualNumBits / 8);
}
else
{
BytesGenerated = number * (ActualNumBits / 8);
}
flush_buffer(BytesGenerated);
if (BytesGenerated + buf_index > MAXBUFFERSIZE)
{
perror("Buffer size not set large enough");
FatalError(FatalErrorInternalError);
end_all();
}
/* we need a workspace where we construct the channels before scaling */
/* the final volume. If we need a bigger one than originally anticipated */
/* we attempt to reallocate it. */
Reallocate:
if (WorkspaceBytes == 0)
{
/* workspace always holds 16-bit values */
WorkspaceBytes = (number * sizeof(short) + 3) & ~3L;
WorkspaceArray1 = (void*)NewPtr(WorkspaceBytes);
WorkspaceArray2 = (void*)NewPtr(WorkspaceBytes);
if ((WorkspaceArray1 == NULL) || (WorkspaceArray2 == NULL))
{
perror("Ran out of memory playing song.");
FatalError(FatalErrorOutOfMemory);
end_all();
}
}
else
{
if (WorkspaceBytes < ((number * sizeof(short) + 3) & ~3L))
{
DisposPtr((void*)WorkspaceArray1);
DisposPtr((void*)WorkspaceArray2);
WorkspaceBytes = 0;
goto Reallocate;
}
}
/* first, erase the workspace, since SampleXXX adds to the workspace. */
WA1Shadow = WorkspaceArray1;
WA2Shadow = WorkspaceArray2;
for (i = WorkspaceBytes / sizeof(long) - 1; i >= 0; i -= 1)
{
/* longs erase faster on 68030 */
((long*)WA1Shadow)[i] = 0;
((long*)WA2Shadow)[i] = 0;
}
if (ActualNumBits == 8)
{
char* BufferAddress;
/* 8-bit sampling */
/* oversample == 1 is merely no antialiasing. I didn't want to fiddle with */
/* Espie's code, so I just use oversample instead of a new variable "antialiased" */
if (oversample == 1)
{
/* WorkspaceArray1 is the left channel, WorkspaceArray2 is the right channel */
SampleAliased8(&(chan[0]),WA1Shadow,number);
SampleAliased8(&(chan[1]),WA2Shadow,number);
SampleAliased8(&(chan[2]),WA2Shadow,number);
SampleAliased8(&(chan[3]),WA1Shadow,number);
}
else
{
SampleAntiAliased8(&(chan[0]),WA1Shadow,number);
SampleAntiAliased8(&(chan[1]),WA2Shadow,number);
SampleAntiAliased8(&(chan[2]),WA2Shadow,number);
SampleAntiAliased8(&(chan[3]),WA1Shadow,number);
}
/* notes about that stuff above: workspace always holds 16-bit values, so */
/* we don't have to fool with ActualNumBits to figure out how many bytes */
/* the workspace should be. */
BufferAddress = &(CurrentBuffer->SampleArea[buf_index]);
if (mac_stereo)
{
do
{
/* Note how we add both the primary and secondary scaling tables. */
/* hence each table is independently clipped. */
/* left channel */
*(BufferAddress++) = FinalVolumeTablePrimary[(*WA1Shadow) & 0x03ff]
+ FinalVolumeTableSecondary[(*WA2Shadow) & 0x03ff] + 128;
/* right channel */
*(BufferAddress++) = FinalVolumeTablePrimary[*(WA2Shadow++) & 0x03ff]
+ FinalVolumeTableSecondary[*(WA1Shadow++) & 0x03ff] + 128;
number -= 1;
} while (number > 0);
}
else
{
do
{
/* for mono, we don't have any secondary sound coming in from */
/* the other channel. */
*(BufferAddress++) = FinalVolumeTablePrimary[(*(WA1Shadow++)
+ *(WA2Shadow++)) & 0x03ff] + 128;
number -= 1;
} while (number > 0);
}
}
else
{
short* BufferAddress;
/* 16 bit sampling */
/* oversample == 1 is merely no antialiasing. I didn't want to fiddle with */
/* Espie's code, so I just use oversample instead of a new variable "antialiased" */
if (oversample == 1)
{
/* WorkspaceArray1 is the left channel, WorkspaceArray2 is the right channel */
SampleAliased16(&(chan[0]),WA1Shadow,number);
SampleAliased16(&(chan[1]),WA2Shadow,number);
SampleAliased16(&(chan[2]),WA2Shadow,number);
SampleAliased16(&(chan[3]),WA1Shadow,number);
}
else
{
SampleAntiAliased16(&(chan[0]),WA1Shadow,number);
SampleAntiAliased16(&(chan[1]),WA2Shadow,number);
SampleAntiAliased16(&(chan[2]),WA2Shadow,number);
SampleAntiAliased16(&(chan[3]),WA1Shadow,number);
}
/* notes about that stuff above: workspace always holds 16-bit values, so */
/* we don't have to fool with ActualNumBits to figure out how many bytes */
/* the workspace should be. */
BufferAddress = (short*)&(CurrentBuffer->SampleArea[buf_index]);
if (mac_stereo)
{
short PrimaryV;
short SecondaryV;
PrimaryV = primary * Loudness;
SecondaryV = secondary * Loudness;
do
{
/* left channel */
/* Primary + Secondary = 256; each buffer has -16384..16383 */
/* in it, Loudness could be 64, so we need to divide by 256*64 */
/* (16384; shift left by 14) */
*(BufferAddress++) = (short)((((*WA1Shadow) * PrimaryV)
+ ((*WA2Shadow) * SecondaryV)) >> 14) /*+ (short)0x8000*/;
/* right channel */
*(BufferAddress++) = (short)((((*(WA2Shadow++)) * PrimaryV)
+ ((*(WA1Shadow++)) * SecondaryV)) >> 14) /*+ (short)0x8000*/;
number -= 1;
} while (number > 0);
}
else
{
short LocalLoudness;
LocalLoudness = Loudness;
do
{
/* Loudness could be as high as 64, and the buffers have */
/* -16384..16383, which, added together, is -32768..32767 */
/* so we need to divide by 64 (shift left by 6) */
*(BufferAddress++) = (short)(
(((long)*(WA1Shadow++) + (long)*(WA2Shadow++)) * LocalLoudness)
>> 6) /*+ (short)0x8000*/;
number -= 1;
} while (number > 0);
}
}
buf_index += BytesGenerated; /* account for added bytes */
}
/* the actual functions for single-channel resampling */
#if !USE_ASSEMBLY_CODE
void SampleAliased8(struct channel* ch, short* Buffer, short Count)
{
long Pointer;
long FixLength;
char* SampleData;
long Step;
long LoopLength;
short Volume;
if (ch->mode != DO_NOTHING)
{
Pointer = ch->pointer;
FixLength = ch->samp->fix_length;
SampleData = ch->samp->start;
Step = ch->step;
LoopLength = ch->samp->fix_rp_length;
Volume = CONSTRAIN(ch->volume,MIN_VOLUME,MAX_VOLUME) - MIN_VOLUME;
LoopPoint:
if (Pointer >= FixLength)
{
/* is there a replay ? */
if (!ch->samp->rp_start)
{
ch->mode = DO_NOTHING;
goto OutPoint;
}
ch->mode = REPLAY;
Pointer -= LoopLength;
goto LoopPoint;
}
*(Buffer++) += (Volume * SampleData[fix_to_int(Pointer)]) >> 6;
Pointer += Step;
Count -= 1;
if (Count > 0)
{
goto LoopPoint;
}
OutPoint:
ch->pointer = Pointer;
}
}
#else
void SampleAliased8(struct channel* ch, short* Buffer, short Count)
{
#define Pointer D0
#define Step D1
#define FixLength D2
#define LocalCount D3
#define Temp D4
#define Temp2 D5
#define SampleData A0
#define VolumeMap A1
#define Channel A2
#define SampPtr A3
#define LocalBuffer A4
asm
{
movem.l D3-D5/A2-A4,-(A7)
/* loading global variables & parameters: Must NOT disturb A5 and A6 here */
move.l ch,Channel
move.w Count,LocalCount
move.l OFFSET(struct channel,volume)(Channel),Temp ;got volume
cmp.l #MIN_VOLUME,Temp
bge.s @8
moveq.l #0,Temp ;if volume < 0, constrain it to 0
@8: cmp.l #MAX_VOLUME,Temp
bmi.s @7
moveq.l #MAX_VOLUME,Temp ;if volume > max volume, constain it to max
@7:
#if 0 != MIN_VOLUME
;this bit is untested, but I think it will work. Don't see why
;MIN_VOLUME would ever be != 0, though
add.l #(MIN_VOLUME * 256 * sizeof(short)),Temp ;table starts at 0, so add this in
#endif
moveq #9,Temp2
lsl.l Temp2,Temp ;multiplied by 256*sizeof(short) to obtain offset
move.l SubVolumeTable,VolumeMap ;get base address
add.l Temp,VolumeMap ;add offset
move.l Buffer,LocalBuffer
subq.w #1,LocalCount
/* loading sample things */
move.l OFFSET(struct channel,samp)(Channel),SampPtr
move.l OFFSET(struct sample_info,fix_length)(SampPtr),FixLength
move.l OFFSET(struct sample_info,start)(SampPtr),SampleData
/* loading channel things */
move.l OFFSET(struct channel,pointer)(Channel),Pointer
move.l OFFSET(struct channel,step)(Channel),Step
/* clearing high bits of certain registers */
cmp.l #DO_NOTHING,OFFSET(struct channel,mode)(Channel)
beq.s @OutPoint
@3: cmp.l Pointer,FixLength
bhi.s @1
tst.l OFFSET(struct sample_info,rp_start)(SampPtr)
bne.s @2
move.l #DO_NOTHING,OFFSET(struct channel,mode)(Channel)
bra.s @OutPoint
@2: sub.l OFFSET(struct sample_info,fix_rp_length)(SampPtr),Pointer
move.l #REPLAY,OFFSET(struct channel,mode)(Channel)
bra.s @3
@1:
move.l Pointer,Temp
moveq #ACCURACY,Temp2
lsr.l Temp2,Temp ;now Temp is the data index
moveq.l #0,Temp2
move.b 0(SampleData,Temp.L),Temp2 ;get byte
#if __option(mc68020)
move.w 0(VolumeMap,Temp2.L*2),Temp ;getting volume corrected value
#else
lsl.l #1,Temp2
move.w 0(VolumeMap,Temp2.L),Temp
#endif
add.w Temp,(LocalBuffer)+ ;adding data into temporary buffer
add.l Step,Pointer ;incrementing pointer
dbf LocalCount,@3
@OutPoint:
move.l Pointer,OFFSET(struct channel,pointer)(Channel)
movem.l (A7)+,D3-D5/A2-A4
}
#undef Pointer
#undef Step
#undef FixLength
#undef LocalCount
#undef Temp
#undef Temp2
#undef SampleData
#undef VolumeMap
#undef Channel
#undef SampPtr
#undef LocalBuffer
}
#endif
#if !USE_ASSEMBLY_CODE
void SampleAntiAliased8(struct channel* ch, short* Buffer, short Count)
{
long Pointer;
long FixLength;
char* SampleData;
long Step;
long LoopLength;
short Volume;
short LeftWeight;
short RightWeight;
if (ch->mode != DO_NOTHING)
{
Pointer = ch->pointer;
FixLength = ch->samp->fix_length;
SampleData = ch->samp->start;
Step = ch->step;
LoopLength = ch->samp->fix_rp_length;
Volume = CONSTRAIN(ch->volume,MIN_VOLUME,MAX_VOLUME) - MIN_VOLUME;
LoopPoint:
if (Pointer >= FixLength)
{
/* is there a replay ? */
if (!ch->samp->rp_start)
{
ch->mode = DO_NOTHING;
goto OutPoint;
}
ch->mode = REPLAY;
Pointer -= LoopLength;
goto LoopPoint;
}
RightWeight = Pointer & ((1 << ACCURACY) - 1);
LeftWeight = (1 << ACCURACY) - RightWeight;
*(Buffer++) +=
(Volume * ((LeftWeight * SampleData[fix_to_int(Pointer)])
+ (RightWeight * SampleData[fix_to_int(Pointer) + 1])) >> (ACCURACY + 1 + 6));
Pointer += Step;
Count -= 1;
if (Count > 0)
{
goto LoopPoint;
}
OutPoint:
ch->pointer = Pointer;
}
}
#else
void SampleAntiAliased8(struct channel* ch, short* Buffer, short Count)
{
#define Pointer D0
#define Mask D1
#define Step D2
#define FixLength D3
#define LocalCount D4
#define Temp D5
#define Temp2 D6
#define SampleData A0
#define AliasMap A1
#define VolumeMap A2
#define Channel A3
#define SampPtr A4
#define LocalBuffer A5
asm
{
movem.l D3-D6/A2-A5,-(A7)
/* loading global variables & parameters: Must NOT disturb A5 and A6 here */
move.l AliasTable,AliasMap
move.l ch,Channel
move.w Count,LocalCount
move.l OFFSET(struct channel,volume)(Channel),Temp ;got volume
cmp.l #MIN_VOLUME,Temp
bge.s @8
moveq.l #0,Temp ;if volume < 0, constrain it to 0
@8: cmp.l #MAX_VOLUME,Temp
bmi.s @7
moveq.l #MAX_VOLUME,Temp ;if volume > max volume, constain it to max
@7:
#if 0 != MIN_VOLUME
;this bit is untested, but I think it will work. Don't see why
;MIN_VOLUME would ever be != 0, though
add.l #(MIN_VOLUME * 256 * sizeof(short)),Temp ;table starts at 0, so add this in
#endif
moveq #9,Temp2
lsl.l Temp2,Temp ;multiplied by 256*sizeof(short) to obtain offset
move.l SubVolumeTable,VolumeMap ;get base address
add.l Temp,VolumeMap ;add offset
move.l Buffer,LocalBuffer
subq.w #1,LocalCount
/* loading sample things */
move.l OFFSET(struct channel,samp)(Channel),SampPtr
move.l OFFSET(struct sample_info,fix_length)(SampPtr),FixLength
move.l OFFSET(struct sample_info,start)(SampPtr),SampleData
/* loading channel things */
move.l OFFSET(struct channel,pointer)(Channel),Pointer
move.l OFFSET(struct channel,step)(Channel),Step
/* clearing high bits of certain registers */
#if __option(mc68020)
;this is an invariant optimization which is only meaningful on the 68020, so
;we don't need it if we are compiling for 68000
clr.l Mask
#endif
cmp.l #DO_NOTHING,OFFSET(struct channel,mode)(Channel)
beq.s @OutPoint
@3: cmp.l Pointer,FixLength
bhi.s @1
tst.l OFFSET(struct sample_info,rp_start)(SampPtr)
bne.s @2
move.l #DO_NOTHING,OFFSET(struct channel,mode)(Channel)
bra.s @OutPoint
@2: sub.l OFFSET(struct sample_info,fix_rp_length)(SampPtr),Pointer
move.l #REPLAY,OFFSET(struct channel,mode)(Channel)
bra.s @3
@1:
;calculating anti-aliasing mask
#if !__option(mc68020)
moveq.l #0,Mask ;make sure to clear the high bits each time for 68000
#endif
move.w Pointer,Mask ;get lower 16 bits of pointer
and.w #0x0f00,Mask ;keep only upper 4 bits of fraction
;this conveniently works out to leave 8 bits just below the accuracy
;into which we can stuff bytes. */
#if ACCURACY != 12
#error "ACCURACY == 12 is hardwired!"
#endif
;fetching data bytes
move.l Pointer,Temp
moveq #ACCURACY,Temp2
lsr.l Temp2,Temp ;now Temp is the data index
moveq.l #0,Temp2
move.b 0(SampleData,Temp.L),Mask ;or in left byte
#if __option(mc68020)
move.b 0(AliasMap,Mask.L*2),Temp2 ;got left product
move.b 1(SampleData,Temp.L),Mask ;or in right byte
add.b 1(AliasMap,Mask.L*2),Temp2 ;added in right product
move.w 0(VolumeMap,Temp2.L*2),Temp ;getting volume corrected value
#else
lsl.l #1,Mask
move.b 0(AliasMap,Mask.L),Temp2 ;got left product
lsr.l #1,Mask ;must shift back, since we OR in a byte next
move.b 1(SampleData,Temp.L),Mask ;or in right byte
lsl.l #1,Mask
add.b 1(AliasMap,Mask.L),Temp2 ;added in right product
lsl.l #1,Temp2
move.w 0(VolumeMap,Temp2.L),Temp ;getting volume corrected value
#endif
add.w Temp,(LocalBuffer)+ ;adding data into temporary buffer
add.l Step,Pointer ;incrementing pointer
dbf LocalCount,@3
@OutPoint:
move.l Pointer,OFFSET(struct channel,pointer)(Channel)
movem.l (A7)+,D3-D6/A2-A5
}
#undef Pointer
#undef Mask
#undef Step
#undef FixLength
#undef LocalCount
#undef Temp
#undef Temp2
#undef SampleData
#undef AliasMap
#undef VolumeMap
#undef Channel
#undef SampPtr
#undef LocalBuffer
}
#endif
/* Same as SampleAliased8 except it doesn't divide out the volume */
void SampleAliased16(struct channel* ch, short* Buffer, short Count)
{
long Pointer;
long FixLength;
char* SampleData;
long Step;
long LoopLength;
short Volume;
if (ch->mode != DO_NOTHING)
{
Pointer = ch->pointer;
FixLength = ch->samp->fix_length;
SampleData = ch->samp->start;
Step = ch->step;
LoopLength = ch->samp->fix_rp_length;
Volume = CONSTRAIN(ch->volume,MIN_VOLUME,MAX_VOLUME) - MIN_VOLUME;
LoopPoint:
if (Pointer >= FixLength)
{
/* is there a replay ? */
if (!ch->samp->rp_start)
{
ch->mode = DO_NOTHING;
goto OutPoint;
}
ch->mode = REPLAY;
Pointer -= LoopLength;
goto LoopPoint;
}
/* placing result in buffer -- ranges between 64*-128 .. 64*128. */
/* that way we can add two channels to get 15 bits worth of stuff */
/* or 4 channels for 16 bits worth of stuff */
*(Buffer++) += Volume * SampleData[fix_to_int(Pointer)];
Pointer += Step;
Count -= 1;
if (Count > 0)
{
goto LoopPoint;
}
OutPoint:
ch->pointer = Pointer;
}
}
/* Same as SampleAntiAliased8 except it doesn't divide out the volume */
void SampleAntiAliased16(struct channel* ch, short* Buffer, short Count)
{
long Pointer;
long FixLength;
char* SampleData;
long Step;
long LoopLength;
short Volume;
short LeftWeight;
short RightWeight;
if (ch->mode != DO_NOTHING)
{
Pointer = ch->pointer;
FixLength = ch->samp->fix_length;
SampleData = ch->samp->start;
Step = ch->step;
LoopLength = ch->samp->fix_rp_length;
Volume = CONSTRAIN(ch->volume,MIN_VOLUME,MAX_VOLUME) - MIN_VOLUME;
LoopPoint:
if (Pointer >= FixLength)
{
/* is there a replay ? */
if (!ch->samp->rp_start)
{
ch->mode = DO_NOTHING;
goto OutPoint;
}
ch->mode = REPLAY;
Pointer -= LoopLength;
goto LoopPoint;
}
RightWeight = Pointer & ((1 << ACCURACY) - 1);
LeftWeight = (1 << ACCURACY) - RightWeight;
/* placing result in buffer -- ranges between 64*-128 .. 64*128. */
/* that way we can add two channels to get 15 bits worth of stuff */
/* or 4 channels for 16 bits worth of stuff */
*(Buffer++) +=
(Volume * (((LeftWeight * SampleData[fix_to_int(Pointer)])
+ (RightWeight * SampleData[fix_to_int(Pointer) + 1])) >> (ACCURACY + 1)));
Pointer += Step;
Count -= 1;
if (Count > 0)
{
goto LoopPoint;
}
OutPoint:
ch->pointer = Pointer;
}
}